package fi.pyppe.android.gps;

import java.io.File;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Random;

import org.apache.commons.validator.UrlValidator;

import fi.pyppe.android.gps.listen.PyppeGPSLocationListener;
import fi.pyppe.android.gps.util.KMLWriter;
import fi.pyppe.android.gps.util.Utilities;
import fi.pyppe.android.gps.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.CompoundButton.OnCheckedChangeListener;

enum TargetActivity {
  MAPS,
  UPLOAD,
  EMAIL
}

/**
 * <p>The main Activity.</p>
 * 
 * <p>Shows Location-data, and the start/stop Button.</p>
 * 
 * @author Pyppe
 */
public class PyppeGPSActivity extends Activity implements OnCheckedChangeListener {
  
  // --- User preferences ---
  private boolean preferWirelessNetworks;
  private int minimumSeconds;

  // --- Location-related Listeners and Managers ---
  private PyppeGPSLocationListener gpsLocationListener;
  private PyppeGPSLocationListener wirelessNetworkLocationListener;
  public LocationManager gpsLocationManager;
  public LocationManager wirelessNetworkLocationManager;
  private Location currentLocation;
  private boolean wirelessNetworkEnabled;
  private boolean gpsEnabled;
  private boolean isUsingGps;
  
  // --- KML-writer ---
  private KMLWriter kmlWriter;
  private String currentFileName;

  // --- Misc ---
  private String currentUploadURL;
  private NotificationManager gpsNotifyManager;
  private int NOTIFICATION_ID;


  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    kmlWriter = new KMLWriter(this);
    setContentView(R.layout.main);
    ToggleButton buttonOnOff = (ToggleButton) findViewById(R.id.buttonOnOff);
    buttonOnOff.setOnCheckedChangeListener(this);
    refreshUserPreferences();
  }

  /**
   * Handler for the Start/Stop button.
   */
  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    try {
      TextView buttonLabel = (TextView) findViewById(R.id.buttonOnOffText);
      if (isChecked) {
        refreshUserPreferences();
        notifyStatusbar();
        resetCurrentFileName();
        startLocationManagers();
        buttonLabel.setText("Stop");
      } else {
        removeNotification();
        stopLocationManagers();
        kmlWriter.finishCurrentKML();
        buttonLabel.setText("Start");
      }
      clearForm();
    } catch (Exception ex) {
      setStatusText("Error: " + ex.getMessage());
    }
  }

  /**
   * Manages the notification in the status bar
   */
  private void notifyStatusbar() {
    gpsNotifyManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    showNotificationInStatusbar();
  }

  /**
   * Hides the notification icon in the status bar if it's visible.
   */
  private void removeNotification() {
    try {
      gpsNotifyManager.cancelAll();
    } catch (Exception ex) {
    }
  }

  /**
   * Shows a notification icon in the status bar
   */
  private void showNotificationInStatusbar() {
    // What happens when the notification item is clicked
    Intent contentIntent = new Intent(this, PyppeGPSActivity.class);
    PendingIntent pending = PendingIntent.getActivity(getBaseContext(), 0, contentIntent, Intent.FLAG_ACTIVITY_NEW_TASK);

    Notification notification = new Notification(R.drawable.status25, null, System.currentTimeMillis());
    notification.flags |= Notification.FLAG_ONGOING_EVENT;
    String contentText = PyppeGPSConstants.APP_NAME + " is running.";
    if (currentLocation != null) {
      NumberFormat nf = new DecimalFormat("###.######");
      contentText = nf.format(currentLocation.getLatitude()) + "," + nf.format(currentLocation.getLongitude());
    }

    notification.setLatestEventInfo(getBaseContext(), PyppeGPSConstants.APP_NAME + " is running", contentText, pending);
    gpsNotifyManager.notify(NOTIFICATION_ID, notification);
  }

  /**
   * Refreshes User Preferences.
   */
  public void refreshUserPreferences() {
    SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    preferWirelessNetworks = prefs.getBoolean("preferencePreferWirelessNetworks", false);
    String minimumSecondsString = prefs.getString("preferenceLogFrequency", "5");
    if (minimumSecondsString != null && minimumSecondsString.length() > 0) {
      minimumSeconds = Integer.valueOf(minimumSecondsString);
    } else {
      minimumSeconds = 60;
    }

    TextView logFrequencyText = (TextView) findViewById(R.id.logFrequencyText);
    if (minimumSeconds > 0) {
      logFrequencyText.setText("Every " + minimumSeconds + " seconds");
    } else {
      logFrequencyText.setText("As frequently as possible");
    }

    if ((currentFileName != null && currentFileName.length() > 0)) {
      TextView tvLogFile = (TextView) findViewById(R.id.logFileText);
      tvLogFile.setText("/sdcard/" + PyppeGPSConstants.OUTPUT_FOLDER + "/\n" + currentFileName);
    }
  }

  /**
   * Show Toast-message, when user hides the application with phone's Back-button.
   */
  public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
      moveTaskToBack(true);
      Toast.makeText(getBaseContext(),
          PyppeGPSConstants.APP_NAME + " is still running. You can exit the application from the menu.", Toast.LENGTH_LONG).show();
      return true;
    }
    /*
    if (keyCode == KeyEvent.KEYCODE_D) {
      insertMockGPSData();
      return true;
    }
    */
    return super.onKeyDown(keyCode, event);
  }
  
  @SuppressWarnings("unused")
  private void insertMockGPSData() {
    setStatusText("Started");
    String providerName = "GPS";

    setProviderAndTimeText(providerName + ": " + new Date().toLocaleString());
    String longitude = "22.94";
    String latitude = "61.59";
    
    Random generator = new Random();
    for (int i = 0; i < 13; i++) {
      latitude += generator.nextInt(10);
      longitude += generator.nextInt(10);
    }

    setLatitudeText(longitude);
    setLongitudeText(latitude);

    String altitude = "83." + generator.nextInt(10);
    setAltitudeText(altitude + " m");


    String  speedKmH = "2" + generator.nextInt(4);
    setSpeedText(speedKmH + " km/h");

    if (generator.nextBoolean()) {
      setDirectionText("\u2191");
    } else {
      setDirectionText("\u2197");
    }

    setAccuracyText("\u00b1 4.0 m");
    
    int number = 9 + generator.nextInt(3);
    setSatellitesText(String.valueOf(number));
  }
  

  /**
   * Create Options-menu.
   * 
   * @see options_menu.xml
   */
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.options_menu, menu);
    return true;
  }

  /**
   * Called when one of the menu items is selected.
   */
  public boolean onOptionsItemSelected(MenuItem item) {
    int itemId = item.getItemId();

    switch (itemId) {
    case R.id.menuSettings:
      Intent settingsActivity = new Intent(getBaseContext(), SettingsActivity.class);
      startActivityForResult(settingsActivity, SettingsActivity.REFRESH_PREFERENCES);
      break;
    case R.id.menuTagLocation:
      kmlWriter.showTagLocationDialog();
      break;
    case R.id.menuSendEmail:
      showFilePickerDialog(TargetActivity.EMAIL);
      break;
    case R.id.menuUpload:
      if (isValidUploadURL()) {
        showFilePickerDialog(TargetActivity.UPLOAD);
      } else {
        Utilities.showMessageDialog("Cannot upload", "Please define a valid Upload URL in Settings-menu.", this);
      }
      break;
    case R.id.menuViewMap:
      showFilePickerDialog(TargetActivity.MAPS);
      break;
    case R.id.menuExit:
      removeNotification();
      stopLocationManagers();
      System.exit(0);
      break;
    }
    return false;
  }
  
  private boolean isValidUploadURL() {
    SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
    currentUploadURL = preferences.getString("preferenceUploadURL", "http://");
    
    if (currentUploadURL.equals("http://")) {
      return false;
    }
    
    String[] schemes = {"http","https"};
    UrlValidator urlValidator = new UrlValidator(schemes);
    if (urlValidator.isValid(currentUploadURL)) {
      return true;
    } else {
      return false;
    }
  }
  
  private void showFilePickerDialog(TargetActivity targetActivity) {
    try {
      final File gpxFolder = new File(Environment.getExternalStorageDirectory(), PyppeGPSConstants.OUTPUT_FOLDER);
      final TargetActivity activity = targetActivity;
      if (gpxFolder.exists()) {
        String[] enumeratedFiles = gpxFolder.list();
        List<String> fileList = new ArrayList<String>(Arrays.asList(enumeratedFiles));
        Collections.sort(fileList);
        Collections.reverse(fileList);
        final String[] files = fileList.toArray(new String[0]);
        final Dialog dialog = new Dialog(this);
        if (activity.equals(TargetActivity.UPLOAD)) {
          dialog.setTitle("Choose file to upload");
        } else if (activity.equals(TargetActivity.MAPS)) {
          dialog.setTitle("Choose file to view");
        } else {
          dialog.setTitle("Choose file to email");
        }
        dialog.setContentView(R.layout.filelist);
        
        ListView thelist = (ListView) dialog.findViewById(R.id.listViewFiles);
        thelist.setAdapter(new ArrayAdapter<String>(getBaseContext(), android.R.layout.simple_list_item_single_choice, files));
        thelist.setOnItemClickListener(new OnItemClickListener() {
          @SuppressWarnings("unchecked")
          public void onItemClick(AdapterView av, View v, int index, long arg) {
            dialog.dismiss();
            final String chosenFileName = files[index];
            if (activity.equals(TargetActivity.UPLOAD)) {
              final AlertDialog alertDialog = new AlertDialog.Builder(PyppeGPSActivity.this).create();
              alertDialog.setTitle("Upload");
              final EditText input = new EditText(PyppeGPSActivity.this);
              alertDialog.setView(input);
              alertDialog.setMessage("Give description of file:");
              alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  Intent settingsActivity = new Intent(getBaseContext(), UploadActivity.class);
                  settingsActivity.putExtra(UploadActivity.FILE_DESCRIPTION, input.getText().toString());
                  settingsActivity.putExtra(UploadActivity.FILE_DATA, new File(gpxFolder, chosenFileName));
                  settingsActivity.putExtra(UploadActivity.UPLOAD_URL, currentUploadURL);
                  startActivity(settingsActivity);
                  return;
                }
              });
              alertDialog.setButton2("Cancel", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                  // Canceled.
                }
              });
              alertDialog.show();
            } else if (activity.equals(TargetActivity.MAPS)) {
              Intent mapsActivity = new Intent(getBaseContext(), MapsActivity.class);
              mapsActivity.putExtra("kmlFile", new File(gpxFolder, chosenFileName));
              startActivity(mapsActivity);
              return;
            } else {
              Intent sendEmailIntent = new Intent(Intent.ACTION_SEND);
              sendEmailIntent.putExtra(Intent.EXTRA_SUBJECT, "Data from " + PyppeGPSConstants.APP_NAME);
              sendEmailIntent.putExtra(Intent.EXTRA_STREAM, 
                  Uri.parse("file://" + Environment.getExternalStorageDirectory() + "/" + PyppeGPSConstants.OUTPUT_FOLDER + "/" + chosenFileName ));
              sendEmailIntent.setType("application/vnd.google-earth.kml+xml"); 
              startActivity(Intent.createChooser(sendEmailIntent, "Send mail..."));
            }
          }
        });
        dialog.show();
      }
    } catch (Exception ex) {
      System.out.println(ex.getMessage());
    }
  }
  
  
  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == SettingsActivity.REFRESH_PREFERENCES) {
      refreshUserPreferences();
    }
  }
  
  /**
   * Starts the LocationManagers.
   */
  public void startLocationManagers() {
    refreshUserPreferences();
    gpsLocationListener = new PyppeGPSLocationListener(this);
    wirelessNetworkLocationListener = new PyppeGPSLocationListener(this);

    gpsLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    wirelessNetworkLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    checkWirelessNetworkAndGpsStatus();

    if (gpsEnabled && !preferWirelessNetworks) {
      gpsLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minimumSeconds * 1000, 0F, gpsLocationListener);
      gpsLocationManager.addGpsStatusListener(gpsLocationListener);
      isUsingGps = true;
    } else if (wirelessNetworkEnabled) {
      isUsingGps = false;
      wirelessNetworkLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, minimumSeconds * 1000, 0F, wirelessNetworkLocationListener);
    } else {
      isUsingGps = false;
      setStatusText("No Location-provider available");
      return;
    }

    setStatusText("Started");
  }

  
  private void checkWirelessNetworkAndGpsStatus() {
    wirelessNetworkEnabled = wirelessNetworkLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
    gpsEnabled = gpsLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
  }

  /**
   * Stops the LocationManagers.
   */
  private void stopLocationManagers() {

    if (wirelessNetworkLocationListener != null) {
      wirelessNetworkLocationManager.removeUpdates(wirelessNetworkLocationListener);
    }

    if (gpsLocationListener != null) {
      gpsLocationManager.removeUpdates(gpsLocationListener);
      gpsLocationManager.removeGpsStatusListener(gpsLocationListener);
    }

    setStatusText("Stopped");
  }

  /**
   * Sets the current file name based on user preference.
   */
  private void resetCurrentFileName() {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    currentFileName = sdf.format(new Date()) + ".kml";
  }

  /**
   * Clears the table, removes all values.
   */
  private void clearForm() {
    String text = "";
    setLatitudeText(text);
    setLongitudeText(text);
    setProviderAndTimeText(text);
    setAltitudeText(text);
    setSpeedText(text);
    setSatellitesText(text);
    setDirectionText(text);
    setAccuracyText(text);
  }

  /**
   * Sets the Location Provider status label.
   * 
   * @param message
   *          Status message
   */
  public void setStatusText(String message) {
    TextView textView = (TextView) findViewById(R.id.statusText);
    textView.setText(message);
  }

  /**
   * Shows the number of Satellites in the UI.
   * 
   * @param number
   *          Number of satellites
   */
  public void setNumberOfSatellites(int number) {
    TextView textView = (TextView) findViewById(R.id.satellitesText);
    textView.setText(String.valueOf(number));
  }
  
  private void setLatitudeText(String text) {
    TextView textView = (TextView) findViewById(R.id.latitudeText);
    textView.setText(text);
  }
  
  private void setLongitudeText(String text) {
    TextView textView = (TextView) findViewById(R.id.longitudeText);
    textView.setText(text);
  }
  
  private void setProviderAndTimeText(String text) {
    TextView textView = (TextView) findViewById(R.id.providerAndTimeText);
    textView.setText(text);
  }
  
  private void setAltitudeText(String text) {
    TextView textView = (TextView) findViewById(R.id.altitudeText);
    textView.setText(text);
  }
  
  private void setSpeedText(String text) {
    TextView textView = (TextView) findViewById(R.id.speedText);
    textView.setText(text);
  }
  
  private void setSatellitesText(String text) {
    TextView textView = (TextView) findViewById(R.id.satellitesText);
    textView.setText(text);
  }
  
  private void setDirectionText(String text) {
    TextView textView = (TextView) findViewById(R.id.directionText);
    textView.setText(text);
  }
  
  private void setAccuracyText(String text) {
    TextView textView = (TextView) findViewById(R.id.accuracyText);
    textView.setText(text);
  }
  


  /**
   * Update current location.
   * 
   * @param location
   *          New Location
   */
  public void updateLocation(Location location) {
    currentLocation = location;
    try {
      notifyStatusbar();
      
      String providerName = location.getProvider();
      if (providerName.equalsIgnoreCase(LocationManager.GPS_PROVIDER)) {
        providerName = "GPS";
      } else {
        providerName = "Wireless networks";
      }

      setProviderAndTimeText(providerName + ": " + new Date().toLocaleString());
      setLatitudeText(String.valueOf(location.getLatitude()));
      setLongitudeText(String.valueOf(location.getLongitude()));

      if (location.hasAltitude()) {
        double altitude = location.getAltitude();
        setAltitudeText(String.valueOf(altitude) + " m");
      } else {
        setAltitudeText("-");
      }

      if (location.hasSpeed()) {
        int speedKmH = (int) (location.getSpeed() * 3.6);
        setSpeedText(String.valueOf(speedKmH) + " km/h");
      } else {
        setSpeedText("-");
      }

      setDirectionText(Utilities.getDirectionArrow(location));

      if (!isUsingGps) {
        setSatellitesText("-");
      }

      if (location.hasAccuracy()) {
        float accuracy = location.getAccuracy();
        setAccuracyText("\u00b1 " + String.valueOf(accuracy) + " m");
      } else {
        setAccuracyText("-");
      }

      kmlWriter.writeToFile(location);
      refreshUserPreferences();
      resetManagersIfRequired();

    } catch (Exception ex) {
      setStatusText("Error in displaying location info: " + ex.getMessage());
    }

  }
  
  public String getCurrentFileName() {
    return currentFileName;
  }
  
  public Location getCurrentLocation() {
    return currentLocation;
  }

  /**
   * Stops GPS Location Manager, then starts it.
   */
  public void restartLocationManagers() {
    stopLocationManagers();
    startLocationManagers();
  }

  /**
   * Checks to see if providers have been enabled and switches providers based
   * on user preferences.
   */
  private void resetManagersIfRequired() {
    checkWirelessNetworkAndGpsStatus();
    if (isUsingGps && preferWirelessNetworks) {
      restartLocationManagers();
    } else if (gpsEnabled && !isUsingGps && !preferWirelessNetworks) {
      restartLocationManagers();
    }
  }

}
